Insights - Tabela de Dados da Aeronaútica
Introdução
A ideia desse documento é pegar um dataset com qual não tenho familiaridade e tentar extrair algum tipo de insight ou algum fato interessante usando R, usando especialmente data.table, Rmarkdown para compilar um documento de como fui me mergulhando nos dados junto com alguns htmlwidgets no caminho, e possivelmente criar um demo de um aplicativo shiny no final.
Nesse caso, escolhi um dataset que me interessou sobre Ocorrências Aeronáuticas na Aviação Civil Brasileira. Imagino que terá como mexer um pouco com mapas usando Leatlet ou alguma outra direção que quisermos tomar.
Esses datasets são extraídos da CENIPA e estão disponíveis no site de dados abertos do Governo Federal.
Segue uma breve descrição do que está no site:
A base de dados de ocorrências aeronáuticas é gerenciada pelo Centro de Investigação e Prevenção de Acidentes Aeronáuticos (CENIPA). Constam nesta base de dados as ocorrências aeronáuticas notificadas ao CENIPA nos últimos 10 anos e que ocorreram em solo brasileiro.
Dentre as informações disponíveis estão os dados sobre as aeronaves envolvidas, fatalidades, local, data, horário dos eventos e informações taxonômicas típicas das investigações de acidentes (AIG). São resguardadas a privacidade de pessoas físicas/jurídicas envolvidas conforme previsto pela Lei de Acesso à Informação (Lei n° 12.527, de 18 de novembro de 2011).
- Informações dos dados utilizados nesse relatório:
| Campo | Valor |
|---|---|
| Fonte | http://www.fab.mil.br/cenipa/ |
| Autor | Centro de Investigação e Prevenção de Acidentes Aeronáuticos |
| Mantenedor | Centro de Investigação e Prevenção de Acidentes Aeronáuticos |
| Versão | 1.3 |
| Última Atualização | 5 de Outubro de 2021, 19:19 (UTC-03:00) |
| Criado | 1 de Junho de 2015, 15:37 (UTC-03:00) |
| Cobertura geográfica | Brasil |
| Cobertura temporal | 2010 a 2019 |
| Fale Conosco | estatistica.cenipa@fab.mil.br |
| Frequência de atualização | Anual |
| Granularidade geográfica | Aeródromo |
| Granularidade temporal | Hora:Minuto |
| VCGE | Aeronáutica [http://vocab.e.gov.br/2011/03/vcge#aeronautica], Transporte Aéreo [http://vocab.e.gov.br/2011/03/vcge#transporte-aereo] |
Importação e Tratamentos Iniciais dos Dados
Importação dos Dados
Bom, iniciei o programa importando as informações do portal da CENIPA (Centro de Investigação e Prevenção de Acidentes Aeronáuticos) encontrados no link mencionados na Introdução.
Note que se importarmos o programa sem especificar o encoding como “UTF-8”, então caracteres especiais do Português Brasil serão lidos incorretamente. e.g. “ã”, “é”, “Ç”, etc.
Cruzamento dos Dados
Explicação
Normalmente, essa seria um bom momento de dar uma olhada em algumas colunas e ver o que podemos fazer com elas. No entanto, lendo as informações no site de dados do Governo Federal, notei que apesar de termos 5 tabelas diferentes, há uma tabela “central” que possui informações adicionais distribuídas nas outras 4, assim como na imagem a seguir:
Como as tabelas são bem pequenas, com menos 10 mil linhas e 30 colunas, cruzá-las e trazer todas as informações em um único dataset poderá facilitar o tratamento de dados mais para frente. Talvez fique um pouco mais difícil de ter uma panorama geral só batendo o olho, mas dado o número colunas parece ser algo tolerável.
Como estou usando o pacote data.table, utilizarei-o para cruzar as informações baseado na chave seguindo a imagem do relacionamento de dados das tabelas acima. Extraindo as informações da imagem acima, seria algo assim:
Cruzamento
Achados sobre a chave
Primeira coisa, dei um head para ter um panorama geral da tabela central, ocorrencia - especialmente das chaves. Nunca se sabe se algo deu errado.
head(ocorrencia[, c("codigo_ocorrencia", "codigo_ocorrencia1", "codigo_ocorrencia2", "codigo_ocorrencia3", "codigo_ocorrencia4")])## codigo_ocorrencia codigo_ocorrencia1 codigo_ocorrencia2 codigo_ocorrencia3
## 1: 40211 40211 40211 40211
## 2: 40349 40349 40349 40349
## 3: 40351 40351 40351 40351
## 4: 39527 39527 39527 39527
## 5: 40324 40324 40324 40324
## 6: 39807 39807 39807 39807
## codigo_ocorrencia4
## 1: 40211
## 2: 40349
## 3: 40351
## 4: 39527
## 5: 40324
## 6: 39807
Opa! Olhando o header, parece que ocorreu algo peculiar. As 5 primeiras linhas das 5 chaves possuem o valor idêntico!
Note que isso não significa que todas as linhas serão iguais. Porém, há uma leve suspeita para saber se isso é muito mais que uma mera coincidência, ou seja, que realmente esteja assim na tabela inteira.
Para checarmos no R se as colunas possuem valores iguais, podemos usar a função identical para saber se os elementos são iguais.
identical(
ocorrencia[, codigo_ocorrencia],
ocorrencia[, codigo_ocorrencia1],
ocorrencia[, codigo_ocorrencia2],
ocorrencia[, codigo_ocorrencia3],
ocorrencia[, codigo_ocorrencia4]
)## [1] TRUE
E olha só! As colunas são de fato idênticas! Pode ser que um dia essas colunas sejam diferentes por algum motivo no site, mas no momento de nossa análise elas são idênticas.
Isso não mudará muita coisa no nosso cruzamento, mas isso significa que podemos descartar alguns dados mais para frente após o cruzamento.
Cruzando as tabelas
Voltando ao que interessa, vamos para o cruzamento. Tentarei fazer 4 left joins seguidos trazendo as informações das 4 tabelas complementares para a tabela central, ocorrencia:
cruzamento1 <- ocorrencia[ocorrencia_tipo, on = "codigo_ocorrencia1"] # Left Join 1
cruzamento2 <- cruzamento1[aeronave, on = "codigo_ocorrencia2"] # Left Join 2
cruzamento3 <- cruzamento2[fator_contribuinte, on = "codigo_ocorrencia3"] # Left Join 3
# tabela <- cruzamento3[recomendacao, on = "codigo_ocorrencia4"] # Left Join 4Rodando o código acima sem a última linha estar comentada, obtive um erro vindo do último cruzamento, um left join entre ocorrencia e recomendacao:
Error in vecseq(f__, len, if (allow.cartesian || notjoin || !anyDuplicated(f, : Join results in 11387 rows; more than 6687 = nrow(x)+nrow(i). Check for duplicate key values in i each of which join to the same group in x over and over again. If that’s ok, try by=.EACHI to run j for each group to avoid the large allocation. If you are sure you wish to proceed, rerun with allow.cartesian=TRUE. Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and data.table issue tracker for advice.
Será que são chaves mesmo?
Aparentemente as chaves de alguma das duas tabelas não são únicas. Vamos dar uma olhadinha na frequências das chaves (código de ocorrência - codigo_ocorrencia) das tabelas:
head(ocorrencia[, .(n = .N), by = codigo_ocorrencia][n > 1][order(-n)])## Empty data.table (0 rows and 2 cols): codigo_ocorrencia,n
head(ocorrencia_tipo[, .(n = .N), by = codigo_ocorrencia1][n > 1][order(-n)])## codigo_ocorrencia1 n
## 1: 66444 3
## 2: 78780 3
## 3: 78879 3
## 4: 78904 3
## 5: 79620 3
## 6: 79651 3
head(aeronave[, .(n = .N), by = codigo_ocorrencia2][n > 1][order(-n)])## codigo_ocorrencia2 n
## 1: 45689 3
## 2: 78249 3
## 3: 79441 3
## 4: 41609 2
## 5: 43869 2
## 6: 44944 2
head(fator_contribuinte[, .(n = .N), by = codigo_ocorrencia3][n > 1][order(-n)])## codigo_ocorrencia3 n
## 1: 41053 19
## 2: 42250 19
## 3: 42921 19
## 4: 44660 18
## 5: 44796 18
## 6: 53340 17
head(recomendacao[, .(n = .N), by = codigo_ocorrencia4][n > 1][order(-n)])## codigo_ocorrencia4 n
## 1: 44796 23
## 2: 45554 20
## 3: 42250 19
## 4: 52265 13
## 5: 66432 12
## 6: 39992 11
Bom, dado que não há uma chave comum entre as tabelas que não estejam duplicadas, não é possível fazer um join simples entre elas como eu gostaria de ter feito. No entanto, como as tabelas não possuem nenhum campo de valor que poderíamos agregar (exceto 3 colunas que contém o nome “total” de frequência, mas que podem ser recalculadas usando as variáveis de origem), é possível fazer algo ainda que nos ajude a colocar tudo numa tabela só, usando cruzamento cartesiano (cartesian join).
Cruzamento Cartesiano
Fazer isso fará com que a tabela após os cruzamentos deixe de ter uma única chave, mas dado que irei sumarizar ela posteriormente com apenas uma seleção das colunas, deve dar tudo certo.
Então, para fazer o cruzamento cartesiano no data.table basta especificar o argumento no final. Vamos ver se agora dará certo…
cruzamento1 <- ocorrencia_tipo[ocorrencia, on = "codigo_ocorrencia1", allow.cartesian = TRUE] # Left Join 1
cruzamento2 <- aeronave[cruzamento1, on = "codigo_ocorrencia2", allow.cartesian = TRUE] # Left Join 2
cruzamento3 <- fator_contribuinte[cruzamento2, on = "codigo_ocorrencia3", allow.cartesian = TRUE] # Left Join 3
tabela <- recomendacao[cruzamento3, on = "codigo_ocorrencia4", allow.cartesian = TRUE] # Left Join 4
head(tabela)## codigo_ocorrencia4 recomendacao_numero recomendacao_dia_assinatura
## 1: 40211 <NA> <NA>
## 2: 40349 <NA> <NA>
## 3: 40351 <NA> <NA>
## 4: 39527 <NA> <NA>
## 5: 39527 <NA> <NA>
## 6: 39527 <NA> <NA>
## recomendacao_dia_encaminhamento recomendacao_dia_feedback
## 1: <NA> <NA>
## 2: <NA> <NA>
## 3: <NA> <NA>
## 4: <NA> <NA>
## 5: <NA> <NA>
## 6: <NA> <NA>
## recomendacao_conteudo recomendacao_status recomendacao_destinatario_sigla
## 1: <NA> <NA> <NA>
## 2: <NA> <NA> <NA>
## 3: <NA> <NA> <NA>
## 4: <NA> <NA> <NA>
## 5: <NA> <NA> <NA>
## 6: <NA> <NA> <NA>
## recomendacao_destinatario codigo_ocorrencia3 fator_nome
## 1: <NA> 40211 <NA>
## 2: <NA> 40349 <NA>
## 3: <NA> 40351 <NA>
## 4: <NA> 39527 APLICAÇÃO DE COMANDOS
## 5: <NA> 39527 JULGAMENTO DE PILOTAGEM
## 6: <NA> 39527 PLANEJAMENTO DE VOO
## fator_aspecto fator_condicionante fator_area
## 1: <NA> <NA> <NA>
## 2: <NA> <NA> <NA>
## 3: <NA> <NA> <NA>
## 4: DESEMPENHO DO SER HUMANO OPERAÇÃO DA AERONAVE FATOR OPERACIONAL
## 5: DESEMPENHO DO SER HUMANO OPERAÇÃO DA AERONAVE FATOR OPERACIONAL
## 6: DESEMPENHO DO SER HUMANO OPERAÇÃO DA AERONAVE FATOR OPERACIONAL
## codigo_ocorrencia2 aeronave_matricula aeronave_operador_categoria
## 1: 40211 PRCHE TÁXI AÉREO
## 2: 40349 PRMAB REGULAR
## 3: 40351 PRMBW REGULAR
## 4: 39527 PTURT ***
## 5: 39527 PTURT ***
## 6: 39527 PTURT ***
## aeronave_tipo_veiculo aeronave_fabricante aeronave_modelo
## 1: HELICÓPTERO SIKORSKY AIRCRAFT S-76C
## 2: AVIÃO AIRBUS INDUSTRIE A320-232
## 3: AVIÃO AIRBUS INDUSTRIE A319-132
## 4: AVIÃO NEIVA INDUSTRIA AERONAUTICA EMB-202
## 5: AVIÃO NEIVA INDUSTRIA AERONAUTICA EMB-202
## 6: AVIÃO NEIVA INDUSTRIA AERONAUTICA EMB-202
## aeronave_tipo_icao aeronave_motor_tipo aeronave_motor_quantidade
## 1: S76 TURBOEIXO BIMOTOR
## 2: A320 JATO BIMOTOR
## 3: A319 JATO BIMOTOR
## 4: IPAN PISTÃO MONOMOTOR
## 5: IPAN PISTÃO MONOMOTOR
## 6: IPAN PISTÃO MONOMOTOR
## aeronave_pmd aeronave_pmd_categoria aeronave_assentos
## 1: 5307 5307 14
## 2: 70000 70000 184
## 3: 75500 75500 154
## 4: 1800 1800 1
## 5: 1800 1800 1
## 6: 1800 1800 1
## aeronave_ano_fabricacao aeronave_pais_fabricante aeronave_pais_registro
## 1: 2007 BRASIL BRASIL
## 2: 2001 BRASIL BRASIL
## 3: 2008 BRASIL BRASIL
## 4: 2004 BRASIL BRASIL
## 5: 2004 BRASIL BRASIL
## 6: 2004 BRASIL BRASIL
## aeronave_registro_categoria aeronave_registro_segmento
## 1: HELICÓPTERO TÁXI AÉREO
## 2: AVIÃO REGULAR
## 3: AVIÃO REGULAR
## 4: AVIÃO PARTICULAR
## 5: AVIÃO PARTICULAR
## 6: AVIÃO PARTICULAR
## aeronave_voo_origem aeronave_voo_destino aeronave_fase_operacao
## 1: FORA DE AERODROMO FORA DE AERODROMO DECOLAGEM
## 2: FORA DE AERODROMO FORA DE AERODROMO DECOLAGEM
## 3: FORA DE AERODROMO FORA DE AERODROMO INDETERMINADA
## 4: FAZENDA IRMÃOS MUNARETTO FAZENDA IRMÃOS MUNARETTO VOO A BAIXA ALTURA
## 5: FAZENDA IRMÃOS MUNARETTO FAZENDA IRMÃOS MUNARETTO VOO A BAIXA ALTURA
## 6: FAZENDA IRMÃOS MUNARETTO FAZENDA IRMÃOS MUNARETTO VOO A BAIXA ALTURA
## aeronave_tipo_operacao aeronave_nivel_dano aeronave_fatalidades_total
## 1: TÁXI AÉREO NENHUM 0
## 2: REGULAR LEVE 0
## 3: REGULAR LEVE 0
## 4: AGRÍCOLA SUBSTANCIAL 0
## 5: AGRÍCOLA SUBSTANCIAL 0
## 6: AGRÍCOLA SUBSTANCIAL 0
## codigo_ocorrencia1 ocorrencia_tipo
## 1: 40211 TRÁFEGO AÉREO
## 2: 40349 COLISÃO COM AVE
## 3: 40351 COLISÃO COM AVE
## 4: 39527 OPERAÇÃO A BAIXA ALTITUDE
## 5: 39527 OPERAÇÃO A BAIXA ALTITUDE
## 6: 39527 OPERAÇÃO A BAIXA ALTITUDE
## ocorrencia_tipo_categoria taxonomia_tipo_icao
## 1: PERDA DE SEPARAÇÃO / COLISÃO EM VOO | TRÁFEGO AÉREO MAC
## 2: COLISÃO COM AVE BIRD
## 3: COLISÃO COM AVE BIRD
## 4: OPERAÇÃO A BAIXA ALTITUDE LALT
## 5: OPERAÇÃO A BAIXA ALTITUDE LALT
## 6: OPERAÇÃO A BAIXA ALTITUDE LALT
## codigo_ocorrencia ocorrencia_classificacao ocorrencia_latitude
## 1: 40211 INCIDENTE ***
## 2: 40349 INCIDENTE
## 3: 40351 INCIDENTE
## 4: 39527 ACIDENTE -13.1066666667
## 5: 39527 ACIDENTE -13.1066666667
## 6: 39527 ACIDENTE -13.1066666667
## ocorrencia_longitude ocorrencia_cidade ocorrencia_uf ocorrencia_pais
## 1: *** RIO DE JANEIRO RJ BRASIL
## 2: BELÉM PA BRASIL
## 3: RIO DE JANEIRO RJ BRASIL
## 4: -55.9930555556 LUCAS DO RIO VERDE MT BRASIL
## 5: -55.9930555556 LUCAS DO RIO VERDE MT BRASIL
## 6: -55.9930555556 LUCAS DO RIO VERDE MT BRASIL
## ocorrencia_aerodromo ocorrencia_dia ocorrencia_hora
## 1: **** 03/01/2010 12:00:00
## 2: SBBE 03/01/2010 11:05:00
## 3: SBRJ 03/01/2010 03:00:00
## 4: **** 04/01/2010 17:30:00
## 5: **** 04/01/2010 17:30:00
## 6: **** 04/01/2010 17:30:00
## investigacao_aeronave_liberada investigacao_status
## 1: SIM FINALIZADA
## 2: SIM FINALIZADA
## 3: SIM FINALIZADA
## 4: SIM FINALIZADA
## 5: SIM FINALIZADA
## 6: SIM FINALIZADA
## divulgacao_relatorio_numero divulgacao_relatorio_publicado
## 1: *** NÃO
## 2: NÃO
## 3: NÃO
## 4: A-539/CENIPA/2018 SIM
## 5: A-539/CENIPA/2018 SIM
## 6: A-539/CENIPA/2018 SIM
## divulgacao_dia_publicacao total_recomendacoes total_aeronaves_envolvidas
## 1: NULL 0 1
## 2: NULL 0 1
## 3: NULL 0 1
## 4: 2019-10-28 0 1
## 5: 2019-10-28 0 1
## 6: 2019-10-28 0 1
## ocorrencia_saida_pista
## 1: NÃO
## 2: NÃO
## 3: NÃO
## 4: NÃO
## 5: NÃO
## 6: NÃO
E sucesso!
Wheeeew.
Agora que tenho uma tabela com todas as informações que preciso, posso criar algumas visões para tirar alguns insights ou análises interessantes sobre. Claro, após tratarmos alguns pontos dela :)
Tratamento dos Dados
Reordenação das Colunas
Para começar os tratamentos dos dados, reordenarei as colunas para ficar na ordem das bases que foram cruzadas, ou seja, na seguinte ordem:
- ocorrencia;
- ocorrencia_tipo;
- aeronave;
- fator_contribuinte;
- recomendacao;
Lembrando que como as colunas codigo_ocorrencia1, codigo_ocorrencia2, codigo_ocorrencia3, codigo_ocorrencia4 já existiam na tabela “central”, ocorrencia, irei removê-las para evitar algum tipo de mal entendido no vetor.
# Extrai o nome (e a ordem) das colunas pré cruzamento
colunas0 <- names(ocorrencia)
colunas1 <- names(ocorrencia_tipo)[2:length(names(ocorrencia_tipo))] # Eliminando a coluna de codigo_ocorrencia1
colunas2 <- names(aeronave)[2:length(names(aeronave))] # Eliminando a coluna de codigo_ocorrencia2
colunas3 <- names(fator_contribuinte)[2:length(names(fator_contribuinte))] # Eliminando a coluna de codigo_ocorrencia3
colunas4 <- names(recomendacao)[2:length(names(recomendacao))] # Eliminando a coluna de codigo_ocorrencia4
# Concatena os nomes das colunas
ordem_colunas <- c(colunas0, colunas1, colunas2, colunas3, colunas4)
# Atualiza a ordem na tabela por referência
data.table::setcolorder(tabela, ordem_colunas)Conversão das Colunas
Após a ordenação, posso finalmente começar a dar uma olhada na tabela “analítica” (pós-cruzamento das tabelas) carinhosamente chamada de tabela. Vamos dar uma olhadinha:
str(tabela)## Classes 'data.table' and 'data.frame': 17646 obs. of 59 variables:
## $ codigo_ocorrencia : int 40211 40349 40351 39527 39527 39527 39527 40324 39807 40215 ...
## $ codigo_ocorrencia1 : int 40211 40349 40351 39527 39527 39527 39527 40324 39807 40215 ...
## $ codigo_ocorrencia2 : int 40211 40349 40351 39527 39527 39527 39527 40324 39807 40215 ...
## $ codigo_ocorrencia3 : int 40211 40349 40351 39527 39527 39527 39527 40324 39807 40215 ...
## $ codigo_ocorrencia4 : int 40211 40349 40351 39527 39527 39527 39527 40324 39807 40215 ...
## $ ocorrencia_classificacao : chr "INCIDENTE" "INCIDENTE" "INCIDENTE" "ACIDENTE" ...
## $ ocorrencia_latitude : chr "***" "" "" "-13.1066666667" ...
## $ ocorrencia_longitude : chr "***" "" "" "-55.9930555556" ...
## $ ocorrencia_cidade : chr "RIO DE JANEIRO" "BELÉM" "RIO DE JANEIRO" "LUCAS DO RIO VERDE" ...
## $ ocorrencia_uf : chr "RJ" "PA" "RJ" "MT" ...
## $ ocorrencia_pais : chr "BRASIL" "BRASIL" "BRASIL" "BRASIL" ...
## $ ocorrencia_aerodromo : chr "****" "SBBE" "SBRJ" "****" ...
## $ ocorrencia_dia : chr "03/01/2010" "03/01/2010" "03/01/2010" "04/01/2010" ...
## $ ocorrencia_hora : chr "12:00:00" "11:05:00" "03:00:00" "17:30:00" ...
## $ investigacao_aeronave_liberada : chr "SIM" "SIM" "SIM" "SIM" ...
## $ investigacao_status : chr "FINALIZADA" "FINALIZADA" "FINALIZADA" "FINALIZADA" ...
## $ divulgacao_relatorio_numero : chr "***" "" "" "A-539/CENIPA/2018" ...
## $ divulgacao_relatorio_publicado : chr "NÃO" "NÃO" "NÃO" "SIM" ...
## $ divulgacao_dia_publicacao : chr "NULL" "NULL" "NULL" "2019-10-28" ...
## $ total_recomendacoes : int 0 0 0 0 0 0 0 0 0 0 ...
## $ total_aeronaves_envolvidas : int 1 1 1 1 1 1 1 1 1 1 ...
## $ ocorrencia_saida_pista : chr "NÃO" "NÃO" "NÃO" "NÃO" ...
## $ ocorrencia_tipo : chr "TRÁFEGO AÉREO" "COLISÃO COM AVE" "COLISÃO COM AVE" "OPERAÇÃO A BAIXA ALTITUDE" ...
## $ ocorrencia_tipo_categoria : chr "PERDA DE SEPARAÇÃO / COLISÃO EM VOO | TRÁFEGO AÉREO" "COLISÃO COM AVE" "COLISÃO COM AVE" "OPERAÇÃO A BAIXA ALTITUDE" ...
## $ taxonomia_tipo_icao : chr "MAC" "BIRD" "BIRD" "LALT" ...
## $ aeronave_matricula : chr "PRCHE" "PRMAB" "PRMBW" "PTURT" ...
## $ aeronave_operador_categoria : chr "TÁXI AÉREO" "REGULAR" "REGULAR" "***" ...
## $ aeronave_tipo_veiculo : chr "HELICÓPTERO" "AVIÃO" "AVIÃO" "AVIÃO" ...
## $ aeronave_fabricante : chr "SIKORSKY AIRCRAFT" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" "NEIVA INDUSTRIA AERONAUTICA" ...
## $ aeronave_modelo : chr "S-76C" "A320-232" "A319-132" "EMB-202" ...
## $ aeronave_tipo_icao : chr "S76" "A320" "A319" "IPAN" ...
## $ aeronave_motor_tipo : chr "TURBOEIXO" "JATO" "JATO" "PISTÃO" ...
## $ aeronave_motor_quantidade : chr "BIMOTOR" "BIMOTOR" "BIMOTOR" "MONOMOTOR" ...
## $ aeronave_pmd : int 5307 70000 75500 1800 1800 1800 1800 11990 726 18600 ...
## $ aeronave_pmd_categoria : int 5307 70000 75500 1800 1800 1800 1800 11990 726 18600 ...
## $ aeronave_assentos : chr "14" "184" "154" "1" ...
## $ aeronave_ano_fabricacao : chr "2007" "2001" "2008" "2004" ...
## $ aeronave_pais_fabricante : chr "BRASIL" "BRASIL" "BRASIL" "BRASIL" ...
## $ aeronave_pais_registro : chr "BRASIL" "BRASIL" "BRASIL" "BRASIL" ...
## $ aeronave_registro_categoria : chr "HELICÓPTERO" "AVIÃO" "AVIÃO" "AVIÃO" ...
## $ aeronave_registro_segmento : chr "TÁXI AÉREO" "REGULAR" "REGULAR" "PARTICULAR" ...
## $ aeronave_voo_origem : chr "FORA DE AERODROMO" "FORA DE AERODROMO" "FORA DE AERODROMO" "FAZENDA IRMÃOS MUNARETTO" ...
## $ aeronave_voo_destino : chr "FORA DE AERODROMO" "FORA DE AERODROMO" "FORA DE AERODROMO" "FAZENDA IRMÃOS MUNARETTO" ...
## $ aeronave_fase_operacao : chr "DECOLAGEM" "DECOLAGEM" "INDETERMINADA" "VOO A BAIXA ALTURA" ...
## $ aeronave_tipo_operacao : chr "TÁXI AÉREO" "REGULAR" "REGULAR" "AGRÍCOLA" ...
## $ aeronave_nivel_dano : chr "NENHUM" "LEVE" "LEVE" "SUBSTANCIAL" ...
## $ aeronave_fatalidades_total : int 0 0 0 0 0 0 0 0 0 0 ...
## $ fator_nome : chr NA NA NA "APLICAÇÃO DE COMANDOS" ...
## $ fator_aspecto : chr NA NA NA "DESEMPENHO DO SER HUMANO" ...
## $ fator_condicionante : chr NA NA NA "OPERAÇÃO DA AERONAVE" ...
## $ fator_area : chr NA NA NA "FATOR OPERACIONAL" ...
## $ recomendacao_numero : chr NA NA NA NA ...
## $ recomendacao_dia_assinatura : IDate, format: NA NA ...
## $ recomendacao_dia_encaminhamento: IDate, format: NA NA ...
## $ recomendacao_dia_feedback : chr NA NA NA NA ...
## $ recomendacao_conteudo : chr NA NA NA NA ...
## $ recomendacao_status : chr NA NA NA NA ...
## $ recomendacao_destinatario_sigla: chr NA NA NA NA ...
## $ recomendacao_destinatario : chr NA NA NA NA ...
## - attr(*, ".internal.selfref")=<externalptr>
Hmm…
Analisando com calma a tabela, podemos anotar alguns pontos estranhos:
- De cara, dá para ver algumas colunas que não eram para ser texto: aeronave_ano_fabricacao, ocorrencia_latitude, ocorrencia_dia, etc;
- Não appenas isso: dentre essas colunas, algumas precisam ser convertidas para número enquanto outras para data;
- Além disso, nossa tabela possui alguns valores estranhos como “***“,”NULL” (literalmente como texto, não como valor booleano), que irei inferir que são Missing já que não possuo metadados sobre o dataset.
Como os valores missing influenciará na conversão dos dados de texto para outros tipos, então começarei tratando eles primeiro.
Corrigindo os valores missing
Para corrigir os valores missing das colunas que são do tipo Texto, primeiro:
Filtramos todas as colunas que são texto;
E depois, caso algum elemento se encaixe em alguns dos valores estranhos que vimos, transformamos em missing (NA). e.g. “***“,”NULL”, etc.
Então, temos:
# Seleciona as colunas que são do tipo character (texto), e guarda na colunas_texto
indices_colunas_texto <- which(unlist(lapply(tabela, is.character)))
colunas_texto <- names(tabela)[indices_colunas_texto]
# Para cada coluna do colunas_texto, troca o valor estranho para missing (NA) usando ifelse
tabela[, (colunas_texto) := lapply(.SD, function(x) ifelse(
x == "***"
| x == "****"
| x == "*****"
| x == "******"
| x == "*******"
| x == "********"
| x == "*********"
| x == "****_***"
| x == "****_****"
| x == "NULL"
| x == "",
NA, x)), .SDcols = colunas_texto]Texto para datas
As colunas que deveriam ser do tipo Data contém “dia” no nome:
names(tabela)[grepl("*dia", names(tabela))]## [1] "ocorrencia_dia" "divulgacao_dia_publicacao"
## [3] "recomendacao_dia_assinatura" "recomendacao_dia_encaminhamento"
## [5] "recomendacao_dia_feedback"
de_texto_para_data <- names(tabela)[grepl("*dia", names(tabela))]
str(tabela[, ..de_texto_para_data])## Classes 'data.table' and 'data.frame': 17646 obs. of 5 variables:
## $ ocorrencia_dia : chr "03/01/2010" "03/01/2010" "03/01/2010" "04/01/2010" ...
## $ divulgacao_dia_publicacao : chr NA NA NA "2019-10-28" ...
## $ recomendacao_dia_assinatura : IDate, format: NA NA ...
## $ recomendacao_dia_encaminhamento: IDate, format: NA NA ...
## $ recomendacao_dia_feedback : chr NA NA NA NA ...
## - attr(*, ".internal.selfref")=<externalptr>
Logo, para converter, basta aplicarmos o as.IDate - que é essencialmente o Date normal do R, mas compatível com o data.table.
Só um ponto de atenção: a variável ocorrencia_dia não só está como texto quanto também está em um formato de data diferente das demais. Tirando isso, as coisas estão dando tudo certo!
# Converte somente a coluna ocorrencia_dia por ter um formato diferente (DD/MM/YYYY)
tabela[, ocorrencia_dia := as.IDate(ocorrencia_dia, "%d/%m/%Y")]
# Converte colunas que possuem o formato (YYYY-MM-DD)
tabela[, `:=`(
divulgacao_dia_publicacao = as.IDate(divulgacao_dia_publicacao, "%Y-%m-%d"),
recomendacao_dia_assinatura = as.IDate(recomendacao_dia_assinatura, "%Y-%m-%d"),
recomendacao_dia_encaminhamento = as.IDate(recomendacao_dia_encaminhamento, "%Y-%m-%d"),
recomendacao_dia_feedback = as.IDate(recomendacao_dia_feedback, "%Y-%m-%d")
)]
# Converte a coluna ocorrencia_hora para o formato ITime
tabela[, ocorrencia_hora := as.ITime(ocorrencia_hora, "%d/%m/%Y")]Texto para numérico/inteiro
Agora para o caso de ver qual variável deveria ser numérica, não tem jeito. Como o número é pequeno, dá para darmos uma olhada olhando no “str(tabela)” acima.
- ocorrencia_latitude;
- ocorrencia_longitude;
- aeronave_assentos;
- aeronave_ano_fabricacao;
Sabendo quais são as colunas, basta convertê-las:
colunas_texto_para_numero <- c(
"ocorrencia_latitude",
"ocorrencia_longitude",
"aeronave_assentos",
"aeronave_ano_fabricacao"
)
# tabela[, (colunas_texto_para_numero) := lapply(.SD, function(x) as.numeric(x)), .SDcols = colunas_texto_para_numero]Ou pelo menos deveria ser! Tentando converter as 4 colunas de uma vez retorna erro em 2 colunas - que estão aplicando NA por coercion, ou seja, por não saber converter. Olhando mais afundo, notamos que o problema ocorre nas colunas de latitude e longitude. Portanto, vamos converter as outras duas que deram certo, aeronave_assentos e aeronave_assentos, e ver mais a fundo o caso do da latitude e longitude.
tabela[, `:=`(
aeronave_assentos = as.numeric(aeronave_assentos),
aeronave_ano_fabricacao = as.numeric(aeronave_ano_fabricacao)
)]Latitude e Longitude
Texto
Astericos
# Observações que ainda possuem asteriscos
unique(tabela$ocorrencia_latitude)[grepl("\\*", unique(tabela$ocorrencia_latitude))]## [1] "***-22.98575784" "-14.71083***"
# Observações que ainda possuem asteriscos
unique(tabela$ocorrencia_longitude)[grepl("\\*", unique(tabela$ocorrencia_longitude))]## character(0)
Coordenadas em outro sistema (como DMS - Decimal degrees, minutes and seconds)
tabela[ocorrencia_latitude %like% "”", ocorrencia_latitude]## [1] "15° 39’ 00”S" "15° 39’ 00”S"
tabela[ocorrencia_longitude %like% "”", ocorrencia_longitude]## [1] "056° 07’ 03” W" "056° 07’ 03” W"
Outros casos peculiares
head(sort(unique(tabela[, ocorrencia_latitude])), 135)## [1] "- 22.75944" "-0,889722" "-0.0" "-0.0075"
## [5] "-0.050833333333" "-0.0911111111" "-0.2002777778" "-0.2827778"
## [9] "-0.3061111111" "-0.7333333333" "-0.8669444444" "-0.985"
## [13] "-0.9880555556" "-00.0000" "-01.43778" "-01.5325"
## [17] "-03.76472" "-04.1650" "-04.87139" "-05.05972222"
## [21] "-05.2850" "-05.530555" "-07.04028" "-07.2191666"
## [25] "-08.12638" "-08.126388" "-08.1263888" "-08.12638888"
## [29] "-08.12639" "-08.271666" "-08.713611" "-08.84917"
## [33] "-08.9594444" "-09.53432276" "-09.868888" "-09.96583"
## [37] "-1,3708" "-1,3808" "-1.0870555555" "-1.144722"
## [41] "-1.1966666667" "-1.2388888889" "-1.2941666667" "-1.3333333333"
## [45] "-1.3611111111" "-1.38000" "-1.3801422" "-1.380277777777"
## [49] "-1.38472" "-1.3847222222" "-1.384722222222" "-1.38556"
## [53] "-1.3858333333" "-1.3916666667" "-1.3927777778" "-1.4077777778"
## [57] "-1.4119444444" "-1.41444" "-1.4144444444" "-1.415"
## [61] "-1.41500" "-1.415000" "-1.4169444444" "-1.419722222"
## [65] "-1.4361111111" "-1.437777777777" "-1.4452777778" "-1.46472"
## [69] "-1.4886111111" "-1.532923792" "-1.5922222222" "-1.6386111111"
## [73] "-1.65000" "-1.7213888889" "-1.775" "-1.797777777777"
## [77] "-1.9536111111" "-1.955833" "-1.9813888889" "-10.0061111111"
## [81] "-10.011389" "-10.017222222" "-10.058333" "-10.0619444444"
## [85] "-10.0672222222" "-10.081667" "-10.1025" "-10.10806"
## [89] "-10.182778" "-10.183889" "-10.188187" "-10.19250"
## [93] "-10.2680555556" "-10.29" "-10.290000" "-10.2900000"
## [97] "-10.2916666667" "-10.2961111111" "-10.296389" "-10.3772222222"
## [101] "-10.4144444444" "-10.422500" "-10.4908333333" "-10.4933333333"
## [105] "-10.5261111111" "-10.5305555556" "-10.5972222222" "-10.6002777778"
## [109] "-10.6336111111" "-10.656111" "-10.7275" "-10.7297222222"
## [113] "-10.7338888889" "-10.7405555556" "-10.7555555556" "-10.7641666667"
## [117] "-10.7691666667" "-10.804722" "-10.870556" "-10.8875"
## [121] "-10.8883333333" "-10.9033333333" "-10.927500" "-10.94"
## [125] "-10.94361" "-10.9583333333" "-10.98472" "-10.9852777778"
## [129] "-10.991111" "-100.6775" "-101.827.777.77" "-102.833.333.33"
## [133] "-102.961.111.11" "-103.452.777.77" "-11.009317"
head(sort(unique(tabela[, ocorrencia_longitude])), 40)## [1] "--49.0324242" "--49.239174323" "- 40.68583" "-0.0"
## [5] "-00.0000" "-1000.6775" "-18.2250" "-18.83916"
## [9] "-20.4230" "-23.18166666666" "-25.40555555555" "-32.9919444444"
## [13] "-34.83833" "-34.8425" "-34.85" "-34.87833333"
## [17] "-34.8913888889" "-34.89138889" "-34.8916666667" "-34.9005555556"
## [21] "-34.92277" "-34.922777" "-34.92277777" "-34.922777777"
## [25] "-34.9227777777" "-34.92277777777" "-34.9227777778" "-34.92277778"
## [29] "-34.92278" "-34.927262" "-34.9447222222" "-34.95027"
## [33] "-34.9502777778" "-34.9505555555" "-348.425" "-348.452.777.77"
## [37] "-348.913.888.88" "-349.127.777.77" "-349.227.777.77" "-349.502.777.77"
tail(sort(unique(tabela[, ocorrencia_longitude])), 55)## [1] "-727.694.444.44" "-727.797.222.22" "-8.00872" "000"
## [5] "042.4241" "051,13666" "056° 07’ 03” W" "11.8666666667"
## [9] "14.98400" "16,6372222" "3.0808333333" "34.9094444444"
## [13] "35.5838888889" "38.5322222222" "41.06833" "413.077.777.778"
## [17] "43.98916667" "432.505.555.556" "439.505.555.556" "444.216.666.667"
## [21] "45.8922222222" "46.473055555556" "46.633888888889" "46.6563888889"
## [25] "46.8180555556" "46.9047222222" "46.9436111111" "465.741.666.667"
## [29] "466.341.666.667" "47.0577777778" "47.2680555556" "47.9186111111"
## [33] "48.022222222222" "48.458275" "48.5958333333" "487.575"
## [37] "488.166.666.667" "49.226667" "49.2286" "49.3988888889"
## [41] "49.4686111111" "49.5147222222" "493.494.444.444" "50.0158333333"
## [45] "51.2038888889" "512.880.555.556" "52.685" "53.5522222222"
## [49] "54.4922222222" "54.7241666667" "55.6725" "56.5230555556"
## [53] "63.9027777778" "Longitude: -43." "Longitude: -47."
Texto…
Texto 2…
### Caso 1 - Espaço, e.g. "- 22.75944"
tabela[, ocorrencia_latitude := gsub(" ", "", ocorrencia_latitude)]
tabela[, ocorrencia_longitude := gsub(" ", "", ocorrencia_longitude)]
### Caso 2 - Vírgula, e.g. "-0,889722"
tabela[, ocorrencia_latitude := gsub(",", ".", ocorrencia_latitude)]
tabela[, ocorrencia_longitude := gsub(",", ".", ocorrencia_longitude)]
### Caso 3 - Duplo negativo, e.g. "--49.0324242"
tabela[, ocorrencia_latitude := gsub("--", "-", ocorrencia_latitude)]
tabela[, ocorrencia_longitude := gsub("--", "-", ocorrencia_longitude)]
### Caso 4 - Com a palavra Latitude/Longitude na frente, e.g. "Longitude:-47."
tabela[ocorrencia_latitude %like% "Latitude", ocorrencia_latitude := gsub("Latitude:", "", ocorrencia_latitude)]
tabela[ocorrencia_longitude %like% "Longitude", ocorrencia_longitude := gsub("Longitude:", "", ocorrencia_longitude)]
### Caso 5 - Coordenada em DMS - Decimal degrees, minutes and seconds
tabela[ocorrencia_latitude == "15°39’00”S", ocorrencia_latitude := "-15.650000"]
tabela[ocorrencia_longitude == "056°07’03”W", ocorrencia_longitude := "-56.117500"]
### Caso 6 - Asteriscos na frente ou atrás do número (só Lat)
tabela[ocorrencia_latitude == "***-22.98575784", ocorrencia_latitude := "-22.98575784"]
tabela[ocorrencia_latitude == "-14.71083***", ocorrencia_latitude := "-14.71083"]
### Caso 7 - Possui/termina com S, N, W, ou E, mas não está formatado em coordenadas DMS como no Caso 5
tabela[, ocorrencia_latitude := gsub("S", "", ocorrencia_latitude)]
tabela[, ocorrencia_latitude := gsub("N", "", ocorrencia_latitude)]
tabela[, ocorrencia_longitude := gsub("W", "", ocorrencia_longitude)]
tabela[, ocorrencia_longitude := gsub("E", "", ocorrencia_longitude)]
### Caso 8 - Possui/termina com o símbolo de Grau ("º"/"°")
tabela[, ocorrencia_latitude := gsub("°", "", ocorrencia_latitude)]
tabela[, ocorrencia_longitude := gsub("°", "", ocorrencia_longitude)]
tabela_ <- data.table::copy(tabela)
### Caso 9 - Número cheio de separadores usando ponto ("."). (Lat e Log)
# Cria flag marcando casos que possíveis não estão todos corretos
tabela[, flag_lat_long := fifelse(
lengths(regmatches(ocorrencia_latitude, gregexpr("\\.", ocorrencia_latitude))) > 1
| lengths(regmatches(ocorrencia_longitude, gregexpr("\\.", ocorrencia_longitude))) > 1,
FALSE,
TRUE
)
]
# Latitude
tabela[
lengths(regmatches(ocorrencia_latitude, gregexpr("\\.", ocorrencia_latitude))) > 1,
ocorrencia_latitude := fifelse(
as.numeric(gsub("\\.", "", ocorrencia_latitude)) > 0,
sub("(.{2})(.*)", "\\1.\\2", gsub("\\.", "", ocorrencia_latitude)),
sub("(.{3})(.*)", "\\1.\\2", gsub("\\.", "", ocorrencia_latitude))
)
]
# Longitude
tabela[
lengths(regmatches(ocorrencia_longitude, gregexpr("\\.", ocorrencia_longitude))) > 1,
ocorrencia_longitude := fifelse(
as.numeric(gsub("\\.", "", ocorrencia_longitude)) > 0,
sub("(.{2})(.*)", "\\1.\\2", gsub("\\.", "", ocorrencia_longitude)),
sub("(.{3})(.*)", "\\1.\\2", gsub("\\.", "", ocorrencia_longitude))
)
]
### Após tratamentos, FINALMENTE converte as colunas para numérico
tabela[, `:=`(
ocorrencia_latitude = as.numeric(ocorrencia_latitude),
ocorrencia_longitude = as.numeric(ocorrencia_longitude)
)]
### Caso 10 - 44993 e 50794 não possuem longitude
# Como a ocorrencia 44993 foi na cidade do Rio de Janeiro, aproxima usando as coordenadas
# do Aeroporto Doméstico Santos Dumont: -22.90825417333246, -43.16789880589976
tabela[codigo_ocorrencia == 44993, ocorrencia_longitude := -43.16789880589976]
# Como a ocorrencia 50794 foi na cidade de Turmalina, MG, aproxima usando as coordenadas
# do ponto zero da cidade: -17.285824901708047, -42.731887838264434
tabela[codigo_ocorrencia == 50794, ocorrencia_longitude := -42.731887838264434]Após tratamentos:
head(sort(unique(tabela[, ocorrencia_latitude])), 135)## [1] -2.623500e+07 -2.970250e+02 -2.626750e+02 -2.350750e+02 -2.298750e+02
## [6] -2.292750e+02 -2.282250e+02 -2.229750e+02 -2.044250e+02 -2.043750e+02
## [11] -1.738250e+02 -1.670250e+02 -1.636250e+02 -1.586250e+02 -1.585250e+02
## [16] -1.565250e+02 -1.006775e+02 -9.868889e+01 -9.587500e+01 -9.517222e+01
## [21] -9.367500e+01 -9.256111e+01 -9.228333e+01 -9.033333e+01 -8.959444e+01
## [26] -8.713611e+01 -8.590556e+01 -8.566667e+01 -8.126389e+01 -8.116944e+01
## [31] -8.038333e+01 -7.844444e+01 -7.821389e+01 -7.599444e+01 -7.502500e+01
## [36] -7.398400e+01 -7.358333e+01 -7.229167e+01 -7.160556e+01 -7.148333e+01
## [41] -7.091944e+01 -7.089444e+01 -7.046389e+01 -6.823611e+01 -6.818889e+01
## [46] -6.763889e+01 -6.763056e+01 -6.693056e+01 -6.540278e+01 -6.529167e+01
## [51] -6.086944e+01 -5.908611e+01 -5.868333e+01 -5.368056e+01 -5.248805e+01
## [56] -5.238222e+01 -5.146111e+01 -5.050556e+01 -4.923389e+01 -4.921167e+01
## [61] -4.874444e+01 -4.871389e+01 -4.848056e+01 -4.720278e+01 -4.694361e+01
## [66] -4.519050e+01 -4.250556e+01 -4.244444e+01 -4.162500e+01 -4.074167e+01
## [71] -4.033250e+01 -3.775833e+01 -3.708333e+01 -3.509167e+01 -3.497941e+01
## [76] -3.343667e+01 -3.343639e+01 -3.338472e+01 -3.326889e+01 -3.317694e+01
## [81] -3.314611e+01 -3.249611e+01 -3.244361e+01 -3.241556e+01 -3.240972e+01
## [86] -3.238167e+01 -3.228833e+01 -3.227361e+01 -3.226722e+01 -3.225583e+01
## [91] -3.222194e+01 -3.217611e+01 -3.214972e+01 -3.208167e+01 -3.206444e+01
## [96] -3.198472e+01 -3.196222e+01 -3.193583e+01 -3.171722e+01 -3.158278e+01
## [101] -3.145139e+01 -3.142278e+01 -3.124361e+01 -3.119389e+01 -3.117111e+01
## [106] -3.115528e+01 -3.113153e+01 -3.105167e+01 -3.104111e+01 -3.103361e+01
## [111] -3.102250e+01 -3.099111e+01 -3.090306e+01 -3.087806e+01 -3.084472e+01
## [116] -3.084361e+01 -3.083944e+01 -3.083694e+01 -3.078556e+01 -3.077556e+01
## [121] -3.073056e+01 -3.072778e+01 -3.066194e+01 -3.060556e+01 -3.053806e+01
## [126] -3.050861e+01 -3.047472e+01 -3.044944e+01 -3.044083e+01 -3.041111e+01
## [131] -3.038889e+01 -3.038333e+01 -3.036083e+01 -3.024917e+01 -3.024556e+01
head(sort(unique(tabela[, ocorrencia_longitude])), 40)## [1] -6.013827e+10 -4.819457e+08 -1.000678e+03 -6.689750e+02 -6.006250e+02
## [6] -5.633750e+02 -5.611750e+02 -5.434750e+02 -5.430750e+02 -5.226250e+02
## [11] -5.215750e+02 -5.108250e+02 -5.102750e+02 -5.064750e+02 -4.950750e+02
## [16] -4.855250e+02 -4.599750e+02 -4.583750e+02 -4.502250e+02 -4.501250e+02
## [21] -4.316250e+02 -3.832250e+02 -3.484250e+02 -7.278111e+01 -7.277972e+01
## [26] -7.277972e+01 -7.277972e+01 -7.276944e+01 -7.276944e+01 -7.274528e+01
## [31] -7.270556e+01 -7.168833e+01 -7.168833e+01 -7.164860e+01 -7.048278e+01
## [36] -7.048278e+01 -7.015194e+01 -7.011417e+01 -7.009389e+01 -7.002528e+01
tail(sort(unique(tabela[, ocorrencia_longitude])), 40)## [1] 41.30778 42.42410 43.25056 43.95056 43.98917 44.42167 45.89222
## [8] 46.47306 46.57417 46.63389 46.63417 46.65639 46.81806 46.90472
## [15] 46.94361 47.05778 47.26806 47.91861 48.02222 48.45828 48.59583
## [22] 48.81667 49.22667 49.22860 49.34944 49.39889 49.46861 49.51472
## [29] 50.01583 51.13666 51.20389 51.28806 52.68500 53.55222 54.49222
## [36] 54.72417 55.67250 56.52306 63.90278 487.57500
Após tratamentos…
Análises
Em construção, maybe
Número de ocorrências pelo tempo
Calendário
# Seleciona as colunas relevantes, e dá um distinct para evitar observações duplicadas
ocorrencias_tempo_prep <- unique(tabela[, .(ocorrencia_dia, codigo_ocorrencia)])
#
ocorrencias_tempo <- ocorrencias_tempo_prep[, .(N = .N, ocorrencia_ano = as.character(year(ocorrencia_dia))), keyby = c("ocorrencia_dia")]
ocorrencias_tempo |>
e_charts(ocorrencia_dia)|>
e_calendar(
range = c(min(ocorrencias_tempo$ocorrencia_ano), max(ocorrencias_tempo$ocorrencia_ano)),
# orient = "vertical",
# width = 1,
left = "2.5%",
#top = "15%",
dayLabel = list(show = F),
yearLabel = list(show = F)
) |>
e_heatmap(N, coord_system = "calendar") |>
e_visual_map(max = max(ocorrencias_tempo$N)) |>
e_theme("essos") |>
e_title("Calendar", "Heatmap")Ano a Ano
ocorrencias_tempo[, .(Ocorrências = sum(N)), keyby = ocorrencia_ano] |>
e_charts(ocorrencia_ano) |>
e_line(Ocorrências) |>
e_theme("chalk") |>
e_title("Número de Ocorrências - Ano a ano") |>
e_tooltip(trigger = "axis") Tabela
reactable::reactable(ocorrencias_tempo[, .(N = sum(N)), keyby = ocorrencia_ano])